package io.github.talelin.latticy.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.github.talelin.autoconfigure.exception.ForbiddenException;
import io.github.talelin.latticy.bo.InboundOrderDetailsVo;
import io.github.talelin.latticy.bo.InboundOrderPageVo;
import io.github.talelin.latticy.bo.InboundOrderSkuVo;
import io.github.talelin.latticy.common.LocalUser;
import io.github.talelin.latticy.common.mybatis.Page;
import io.github.talelin.latticy.dto.feign.InboundOrderPageDTO;
import io.github.talelin.latticy.dto.feign.InboundSkuDTO;
import io.github.talelin.latticy.dto.putaway.PutAwayPageDTO;
import io.github.talelin.latticy.dto.receving.*;
import io.github.talelin.latticy.model.*;
import io.github.talelin.latticy.mapper.ReceivingRecordMapper;
import io.github.talelin.latticy.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.github.talelin.latticy.service.feign.InboundOrderFeignService;
import io.github.talelin.latticy.service.feign.ProductFeignService;
import io.github.talelin.latticy.vo.PageResponseVO;
import io.github.talelin.latticy.vo.ReceivingPackageVO;
import io.github.talelin.latticy.vo.ReceivingRecordBySkuVO;
import io.github.talelin.latticy.vo.ReceivingRecordVO;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;

/**
 * <p>
 * 入库收货记录表 服务实现类
 * </p>
 *
 * @author generator@wulongbo
 * @since 2022-07-20
 */
@Service
public class ReceivingRecordServiceImpl extends ServiceImpl<ReceivingRecordMapper, ReceivingRecordDO> implements ReceivingRecordService {
    @Autowired
    private UserwareService userwareService;

    @Autowired
    private InboundOrderFeignService inboundOrderFeignService;

    @Autowired
    private ReceivingBaseInfoService receivingBaseInfoService;

    @Autowired
    private ReceivingRecordItemsService receivingRecordItemsService;

    @Autowired
    private ProductFeignService productService;

    @Autowired
    private StockService stockService;

    @Autowired
    private PutAwayService putAwayService;

    @Autowired
    @Qualifier("threadPoolTaskExecutor")
    private Executor executor;

    public BiPredicate<Integer, Integer> isQuantityMatching = (receiveNum, receivingTotalCount) -> receiveNum.compareTo(receivingTotalCount) == 0;

    @Override
    @GlobalTransactional
    public void createReceivingRecord(ReceivingRecordDTO receivingRecord) {
        List<ReceivingPackageDTO> receivingPackages = receivingRecord.getPackages();
        Assert.notNull(receivingPackages, "18700");
        //1.校验 package本次接收标品数=所有sku标品数量    package本次接收破损数=所有sku破损数量
        this.checkSpecimenAndDamaged(receivingPackages);
        //2.计算提交的sku 本次接收件数=标品数量+破损数量
        this.calculateSkuSpecimenAndDamaged(receivingPackages);
        //3.创建入库单基础信息
        ReceivingBaseInfoDO receivingBaseInfoDO = new ReceivingBaseInfoDO(receivingRecord);
        UpdateWrapper<ReceivingBaseInfoDO> wrapper = new UpdateWrapper<>();
        wrapper.lambda().eq(ReceivingBaseInfoDO::getInboundOrderNo, receivingRecord.getInboundOrderNo());
        receivingBaseInfoService.saveOrUpdate(receivingBaseInfoDO, wrapper);
        //4:创建入库单package信息
        UserDO userDO = LocalUser.getLocalUser();
        List<CompletableFuture<ReceivingRecordDO>> collect = receivingPackages.stream().
                map(pkg -> CompletableFuture.supplyAsync(() -> receivingPackageDTO2ReceivingRecordDO(pkg, receivingRecord.getWarehouseCode(),
                        receivingRecord.getInboundOrderNo(), userDO.getUsername()), executor)).collect(Collectors.toList());
        List<ReceivingRecordDO> receivingRecordDOList = collect.stream().map(CompletableFuture::join).filter(Objects::nonNull).collect(Collectors.toList());
        this.saveBatch(receivingRecordDOList);
        //5：绑定构建id
        receivingPackages.parallelStream().forEach(
                x -> receivingRecordDOList.parallelStream().forEach(
                        y -> {
                            if (x.getPackageNo().equals(y.getPackageNo())) {
                                x.setId(y.getId());
                            }
                        }
                )

        );
        //6:创建入库单item信息
        List<CompletableFuture<List<ReceivingRecordItemsDO>>> itemList = receivingPackages.stream().map(pkg -> CompletableFuture.supplyAsync(() -> receivingSkuCreateDTO2ReceivingRecordItemsDO(pkg), executor)).collect(Collectors.toList());
        List<List<ReceivingRecordItemsDO>> allItemList = itemList.stream().map(CompletableFuture::join).filter(Objects::nonNull).collect(Collectors.toList());
        List<ReceivingRecordItemsDO> idList = allItemList.stream().flatMap(Collection::stream).collect(Collectors.toList());
        receivingRecordItemsService.saveBatch(idList);
        //7.投递消息将在途库存更改为待上架库存(已收货)
        idList.forEach(items -> {
            StockDO stock = stockService.getStockByWarehouseCodeAndCustomerCodeAndSku(receivingRecord.getWarehouseCode(), receivingRecord.getTenant(), items.getSku());
            Assert.notNull(stock, "18514");
            // 存在 累加
            StockDO stockDO = new StockDO();
            stockDO.setId(stock.getId());
            Integer receiveNum = items.getSpecimen() + items.getDamaged();
            stockDO.setOnThePutAway(stock.getOnThePutAway() + receiveNum);
            stockDO.setOnTheWay(stock.getOnTheWay() - receiveNum);
            stockDO.setUpdateTime(new Date());
            Assert.isTrue(stockService.updateById(stockDO), "18512!");
        });

        //8:feign调用修改入库单数据
        inboundOrderFeignService.inboundOrderReceiving(receivingRecord);
    }



    @Override
    public void createReceivingSkuRecord(ReceivingRecordSkuDTO receivingRecordSku) {
        ReceivingRecordDTO receivingRecord=new ReceivingRecordDTO();
        BeanUtils.copyProperties(receivingRecordSku,receivingRecord);
        //1:创建入库单package信息
        List<ReceivingSkuDTO> skus = receivingRecordSku.getSkus();
        Map<String, List<ReceivingSkuDTO>> receivingPackageMap = skus.parallelStream().collect(Collectors.groupingBy(ReceivingSkuDTO::getPackageNo));
        List<ReceivingPackageDTO> receivingPackages = receivingPackageMap.entrySet().parallelStream().map(receivingPackage -> {
            List<ReceivingSkuDTO> receivingSkus = receivingPackage.getValue();
            Integer specimen = receivingSkus.parallelStream().map(ReceivingSkuDTO::getSpecimen).reduce(0, Integer::sum);
            Integer damaged = receivingSkus.parallelStream().map(ReceivingSkuDTO::getDamaged).reduce(0, Integer::sum);
            String packageNo = receivingPackage.getKey();
            ReceivingPackageDTO recordDO = new ReceivingPackageDTO();
            recordDO.setPackageNo(packageNo);
            recordDO.setReceiveNum(specimen + damaged);
            recordDO.setDamaged(damaged);
            recordDO.setSpecimen(specimen);
            recordDO.setReceivedTime(LocalDateTime.now());
            recordDO.setSkus(receivingSkus);
            return recordDO;
        }).collect(Collectors.toList());
        receivingRecord.setPackages(receivingPackages);
        //调用收货接口
        createReceivingRecord(receivingRecord);
    }



    @Override
    @GlobalTransactional
    public boolean redoReceivingRecord(Long[] ids) {
        Assert.notNull(ids, "18707");
        this.checkIds(ids);
        List<ReceivingRecordVO> list = this.baseMapper.getReceivingRecordByIds(ids);
        Assert.isTrue(inboundOrderFeignService.redo(list));
        return this.removeByIds(Arrays.asList(ids));
    }

    @Override
    public IPage<ReceivingRecordVO> getReceivingRecordPage(String inboundOrderNo, Integer count, Integer page) {
        return this.baseMapper.getReceivingRecordPage(new Page<>(page, count), inboundOrderNo);
    }

    @Override
    public ReceivingRecordVO getReceivingRecordList(String inboundOrderNo) {
        return this.baseMapper.getReceivingRecordList(inboundOrderNo);
    }

    @Override
    public PageResponseVO<InboundOrderPageVo> getInboundOrder(InboundOrderPageDTO dto) {
        if (CollUtil.isEmpty(dto.getWarehouseCodes())) {
            List<String> warehouseCodes = userwareService.getWareCodeListByUserId();
            Assert.notNull(warehouseCodes, "18516");
            dto.setWarehouseCodes(warehouseCodes);
        }
        return inboundOrderFeignService.page(dto);
    }

    @Override
    public InboundOrderDetailsVo getInboundOrderByNo(String inboundOrderNo) {
        return inboundOrderFeignService.getInboundOrderInfo(inboundOrderNo);
    }

    @Override
    public InboundOrderSkuVo getInboundOrderSkuByNo(String inboundOrderNo) {
        InboundOrderDetailsVo inboundOrderInfo = inboundOrderFeignService.getInboundOrderInfo(inboundOrderNo);
        List<List<InboundPackagesItemsDO>> collect = inboundOrderInfo.getInboundPackagesList().parallelStream().map(InboundPackagesDO::getInboundPackagesItemsList).collect(Collectors.toList());
        List<InboundSkuDTO> skuDTOList = collect.parallelStream().flatMap(Collection::stream).map(itemsDOS -> {
            InboundSkuDTO inboundSkuDTO = new InboundSkuDTO();
            BeanUtils.copyProperties(itemsDOS,inboundSkuDTO);
            return inboundSkuDTO;
        }).collect(Collectors.toList());
        List<String> skuList = skuDTOList.parallelStream().map(InboundSkuDTO::getSku).distinct().collect(Collectors.toList());
        List<ProductDO> listBySkus = productService.getListBySkus(skuList);
        Assert.notEmpty(listBySkus,"18519");
        Map<String, ProductDO> productMap = listBySkus.parallelStream().collect(Collectors.toMap(ProductDO::getSku, productDO -> productDO, (k1, k2) -> k1));
        skuDTOList.forEach(inboundSkuDTO->{
            ProductDO productDO = productMap.get(inboundSkuDTO.getSku());
            final Integer skuDTOId = inboundSkuDTO.getId();
            BeanUtils.copyProperties(productDO,inboundSkuDTO);
            inboundSkuDTO.setId(skuDTOId);
            inboundSkuDTO.setDistanceUnit(productDO.getLwhType());
            inboundSkuDTO.setMassUnit(productDO.getWeightType());
        });
        InboundOrderSkuVo inboundOrderSkuVo = new InboundOrderSkuVo();
        BeanUtils.copyProperties(inboundOrderInfo,inboundOrderSkuVo);
        inboundOrderSkuVo.setInboundSkuList(skuDTOList);
        return inboundOrderSkuVo;
    }

    @Override
    public ReceivingRecordBySkuVO getBySkuReceivingRecord(String inboundOrderNo) {
        ReceivingRecordVO receivingRecordVO = this.getReceivingRecordList(inboundOrderNo);
        Assert.notNull(receivingRecordVO,"18711");
        List<String> skus = receivingRecordVO.getReceivingPackages().parallelStream().map(ReceivingPackageVO::getRecordItems).flatMap(Collection::parallelStream)
                .map(ReceivingRecordItemsDO::getSku).distinct().collect(Collectors.toList());
        PutAwayPageDTO pageDTO = new PutAwayPageDTO();
        pageDTO.setSkus(skus);
        pageDTO.setInboundOrderNo(inboundOrderNo);
        List<PutAwayDO> putAwayList = putAwayService.getPutAwayList(pageDTO);
        Map<String, Integer> putAwayMap = new HashMap<>();
        if(CollectionUtils.isNotEmpty(putAwayList)){
            putAwayMap=putAwayList.parallelStream().collect(Collectors.groupingBy(PutAwayDO::getSku, Collectors.summingInt(PutAwayDO::getQuantity)));
        }
        Map<String, List<ReceivingRecordItemsDO>> receivingRecordItemsBySkuMap = receivingRecordVO.getReceivingPackages().parallelStream().map(ReceivingPackageVO::getRecordItems).flatMap(Collection::parallelStream).collect(Collectors.groupingBy(ReceivingRecordItemsDO::getSku));
        Map<String, Integer> finalPutAwayMap = putAwayMap;
        List<ReceivingRecordItemsDO> recordItemsDOList = receivingRecordItemsBySkuMap.entrySet().parallelStream().map(recordItems ->receivingRecordItemsDO(recordItems.getValue(), finalPutAwayMap)).collect(Collectors.toList());
        ReceivingRecordBySkuVO recordBySkuVO=new ReceivingRecordBySkuVO();
        recordBySkuVO.setInboundOrderNo(inboundOrderNo);
        recordBySkuVO.setRecordItems(recordItemsDOList);
        return recordBySkuVO;
    }

    private ReceivingRecordItemsDO receivingRecordItemsDO(List<ReceivingRecordItemsDO> recordItemsDOS,Map<String, Integer> putAwayMap){
        ReceivingRecordItemsDO receivingRecordItemsDO = new ReceivingRecordItemsDO();
        if(CollectionUtils.isNotEmpty(recordItemsDOS)) {
            Integer damaged = recordItemsDOS.parallelStream().map(ReceivingRecordItemsDO::getDamaged).reduce(0, Integer::sum);
            Integer specimen = recordItemsDOS.parallelStream().map(ReceivingRecordItemsDO::getSpecimen).reduce(0, Integer::sum);
            if(MapUtils.isNotEmpty(putAwayMap)&&putAwayMap.containsKey(recordItemsDOS.get(0).getSku())){
                specimen -= putAwayMap.get(recordItemsDOS.get(0).getSku());
            }
            receivingRecordItemsDO.setSku(recordItemsDOS.get(0).getSku());
            receivingRecordItemsDO.setSkuName(recordItemsDOS.get(0).getSkuName());
            receivingRecordItemsDO.setDamaged(damaged);
            receivingRecordItemsDO.setSpecimen(specimen);
        }
        return receivingRecordItemsDO;
    }

    public void calculateSkuSpecimenAndDamaged(List<ReceivingPackageDTO> receivingPackages) {
        receivingPackages.forEach(this::calculateReceiveNum);
    }

    public void calculateReceiveNum(ReceivingPackageDTO pkg) {
        pkg.getSkus().parallelStream().map(
                x -> {
                    x.setReceiveNum(x.getSpecimen() + x.getDamaged());
                    return x;
                }
        ).collect(Collectors.toList());
    }

    private void checkSpecimenAndDamaged(List<ReceivingPackageDTO> receivingPackages) {
        receivingPackages.forEach(this::checkReceiveNum);
    }

    private void checkReceiveNum(ReceivingPackageDTO pkg) {
        Integer specimen = pkg.getSkus().parallelStream()
                .map(ReceivingSkuDTO::getSpecimen)
                // 使用reduce()聚合函数,实现累加器
                .reduce(0, Integer::sum);
        if (!isQuantityMatching.test(pkg.getSpecimen(), specimen)) {
            throw new ForbiddenException(18702);
        }
        Integer damaged = pkg.getSkus().parallelStream()
                .map(ReceivingSkuDTO::getDamaged)
                // 使用reduce()聚合函数,实现累加器
                .reduce(0, Integer::sum);
        if (!isQuantityMatching.test(pkg.getDamaged(), damaged)) {
            throw new ForbiddenException(18703);
        }
        pkg.setReceiveNum(pkg.getSpecimen() + pkg.getDamaged());
    }

    private ReceivingRecordDO receivingPackageDTO2ReceivingRecordDO(ReceivingPackageDTO dto, String warehouseCode, String inboundOrderNo, String receivedBy) {
        return new ReceivingRecordDO(dto, warehouseCode, inboundOrderNo, receivedBy);
    }

    private List<ReceivingRecordItemsDO> receivingSkuCreateDTO2ReceivingRecordItemsDO(ReceivingPackageDTO dto) {
        return dto.getSkus().parallelStream().map(item -> new ReceivingRecordItemsDO(item, dto.getId())).collect(Collectors.toList());
    }

    private List<ReceivingRecordItemsDO> receivingSkuCreateDTO2ReceivingRecordItemsDO1(ReceivingPackageDTO dto) {
        return dto.getSkus().parallelStream().map(item -> new ReceivingRecordItemsDO(item, dto.getId())).collect(Collectors.toList());
    }

    private void checkIds(Long[] ids) {
        QueryWrapper<ReceivingRecordDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().in(ReceivingRecordDO::getId, ids);
        int count = this.count(queryWrapper);
        if (!isQuantityMatching.test(count, ids.length)) {
            throw new ForbiddenException(18708);
        }
    }

}
